package repo

import (
	"database/sql"
	"errors"
	"fmt"
	"github.com/jinzhu/gorm"
	log "github.com/sirupsen/logrus"
	model "micro-oauth/datamodels"
	"micro-oauth/datamodels/autherror"
	"micro-oauth/datamodels/params"
	"micro-oauth/repo/cache"
	"strings"
	"time"
)

// 缓存思路：
// 1. 获取get user roles (把user_id---> role_id放进redis），设置缓存时间，暂时不需要搞反向缓存。
// 2. 根据role_id获取permission_code, 但是permission code可能改变，所以需要获取一个反向缓存，permission_id to role_id,
// 当permission_id 更新时，清空role_id.
// 3. 同时要提供重建缓存的API
// 如果需要在黄金但是这种思路依然需要，hit db,获取

// 另一种思路：
// 直接缓存user 的所有数据，维护一个role_id到user_id的反向缓存，
// 维护一个permission到user_id的反向缓存，当role或permission有变动时，统一清空user数据。(重建redis数据）

type ServiceImpl struct {
	db           *gorm.DB
	cacheService cache.RepoCacheServiceInterface
}

func NewService(db *gorm.DB, _cacheService cache.RepoCacheServiceInterface) ServiceInterface {
	db.SingularTable(true)
	db = db.LogMode(true).
		Set("gorm:association_autoupdate", false).
		Set("gorm:association_autocreate", false).
		Set("gorm:association_save_reference", true).Unscoped()
	var service = ServiceImpl{
		db:           db,
		cacheService: _cacheService,
	}
	return &service
}

func (s *ServiceImpl) Close() {
	_ = s.db.Close()
	s.cacheService.Close()
}

func (s *ServiceImpl) GetPartnerById(partnerId uint64) *model.Partner {
	var partner model.Partner
	// 如果找不到，会返回err
	err := s.db.Where("id = ?", partnerId).First(&partner).Error
	if err != nil {
		return nil
	}
	return &partner
}

func (s *ServiceImpl) MigrateTables() error {
	return s.db.AutoMigrate(
		&model.Partner{}, &model.User{}, &model.Role{}, &model.Client{}, &model.Token{},
		&model.Permission{}, &model.API{}, &model.Scope{},
		&model.RolePermissionInfo{},
		//&model.Device{},&model.Method{},
	).Error
}

func (s *ServiceImpl) DeletePartner(partnerId uint64) error {
	return s.db.Unscoped().Where(
		"id = ?", partnerId,
	).Delete(model.Partner{}).Error
}

func (s *ServiceImpl) AddPartner(partner *model.Partner) error {
	return s.db.Create(partner).Error
}

func (s *ServiceImpl) UpdatePartner(partner *model.Partner) error {
	return s.db.Save(partner).Error
}

func (s *ServiceImpl) ListPartner(req *params.PartnerListRequest) (partners []*model.Partner, total uint64, err error) {
	db := DefaultListDB(s.db, &req.ListRequest)
	if req.Name != "" {
		db = db.Where("name = ?", req.Name)
	}
	if req.Code != "" {
		db = db.Where("code = ?", req.Code)
	}
	db.Model(model.Partner{}).Count(&total)
	db = LimitDB(db, req.Limit, req.Offset)
	err = db.Find(&partners).Error
	if err != nil || len(partners) == 0 {
		return make([]*model.Partner, 0), 0, err
	}
	return partners, total, nil
}

func (s *ServiceImpl) GetUserRoles(partnerId, userId uint64) []*model.Role {
	var user model.User
	var emptyResult = make([]*model.Role, 0)
	err := s.db.Preload("Roles").
		Select("id").
		Where("partner_id = ? and id = ?", partnerId, userId).
		First(&user).Error
	if err != nil || len(user.Roles) == 0 {
		return emptyResult
	}
	return user.Roles
}

func (s *ServiceImpl) GetUserRoleIds(userId uint64) []uint64 {
	roleIds, err := s.cacheService.GetUserRoleIds(userId)
	if err != nil {
		LogError(err, "get user role ids from cache")
	} else {
		return roleIds
	}
	roleIds = make([]uint64, 0)
	err = s.db.Table("oauth_user_role_relation").
		Where("user_id = ?", userId).Pluck("role_id", &roleIds).Error
	LogError(err, "get user role ids")
	err = s.cacheService.SetUserRoleIds(userId, roleIds)
	LogError(err, "set user role ids to cache")
	return roleIds
}

func (s *ServiceImpl) ListPermissions(req *params.PermissionListRequest) (permissions []*model.Permission, total uint64, err error) {
	db := DefaultListDB(s.db, &req.ListRequest)
	if req.Name != "" {
		db = db.Where("name = ?", req.Name)
	}
	if req.Code != "" {
		db = db.Where("code = ?", req.Code)
	}
	if len(req.RoleIds) != 0 {
		db = db.Joins("join oauth_role_permission_info as rpi on rpi.permission_id = oauth_permission.id").
			Where("rpi.role_id in (?)", req.RoleIds)
	}
	if len(req.ParentIdList) != 0 {
		db = db.Where("parent_id in (?)", req.ParentIdList)
	}
	db.Model(&model.Permission{}).Count(&total)
	if req.Limit != 0 {
		db = db.Limit(req.Limit)
	}
	if req.Offset != 0 {
		db = db.Offset(req.Offset)
	}
	err = db.Model(&model.Permission{}).Find(&permissions).Error
	if err != nil || len(permissions) == 0 {
		return make([]*model.Permission, 0), total, err
	}
	return permissions, total, nil
}

func IdSliceToStr(ids []uint64) string {
	result := make([]string, len(ids))
	for i, id := range ids {
		result[i] = fmt.Sprintf("%d", id)
	}
	return strings.Join(result, ",")
}

func (s *ServiceImpl) ListRolePermissionCodes(roleIds []uint64) (permissionCodes []string, err error) {
	if len(roleIds) == 0 {
		return make([]string, 0), nil
	}
	err = s.db.Model(&model.Permission{}).
		Joins("inner join oauth_role_permission_relation as rp on rp.permission_id = oauth_permission.id").
		Where("rp.role_id in (?)", roleIds).
		Pluck("code", &permissionCodes).Error
	if err != nil {
		return nil, err
	}
	return permissionCodes, nil
}

func (s *ServiceImpl) GetUserDetailData(partnerId, userId uint64) (userDetail *model.UserDetailData, err error) {
	user := s.GetUserById(userId)
	if user == nil {
		return nil, errors.New("not found")
	}
	roleIds := s.GetUserRoleIds(user.Id)
	var permissionCodes = make([]string, 0)
	for _, roleId := range roleIds {
		permissionInfos, err := s.ListRolePermissionInfos(roleId)
		if err != nil {
			LogError(err, "get role permission info ids")
			continue
		}
		for _, p := range permissionInfos {
			permissionCodes = append(permissionCodes, p.PermissionCode)
		}
	}
	user.PermissionCodes = permissionCodes
	return &model.UserDetailData{User: user}, nil
}

func (s *ServiceImpl) GetRolePermissionCodes(roleId uint64) ([]uint64, error) {
	var permissionInfoIds = make([]uint64, 0)
	err := s.db.Table("oauth_role_permission_info").
		Where("role_id = ?", roleId).
		Pluck("permission_id", &permissionInfoIds).Error
	if err != nil {
		return nil, err
	}
	return permissionInfoIds, nil
}

func (s *ServiceImpl) GetUserDetailFromRDB(partnerId, userId uint64) (user *model.User, err error) {
	user = s.GetUserById(userId)
	if user == nil {
		return nil, autherror.NotFound("can not found")
	}
	roles := s.GetUserRoles(user.PartnerId, userId)
	var roleIds = make([]uint64, len(roles))
	for i, role := range roles {
		roleIds[i] = role.Id
	}
	if len(roleIds) == 0 {
		return user, nil
	}

	var permissionInfos = make([]*model.RolePermissionInfo, 0)
	sql := `select RPI.role_id, RPI.permission_id, RPI.need_auth, oauth_permission.code 
        from oauth_role_permission_info AS RPI
		inner join oauth_permission on oauth_permission.id = RPI.permission_id
		where role_id in (?)`
	rows, err := s.db.Raw(sql, roleIds).Rows()
	if err != nil {
		return nil, err
	}
	defer func() {
		if err := rows.Close(); err != nil {
			log.Error("close rows error: " + err.Error())
		}
	}()
	for rows.Next() {
		var roleId uint64
		var permissionId uint64
		var needOauth bool
		var permissionCode string
		err := rows.Scan(&roleId, &permissionId, &needOauth, &permissionCode)
		if err != nil {
			return nil, err
		}
		permissionInfos = append(permissionInfos, &model.RolePermissionInfo{
			RoleId:         roleId,
			PermissionId:   permissionId,
			NeedAuth:       needOauth,
			PermissionCode: permissionCode,
		})
	}

	user.Roles = roles
	user.PermissionInfos = permissionInfos
	return user, nil
}

func (s *ServiceImpl) AddPermission(permission *model.Permission) error {
	return s.db.Create(permission).Error
}

func (s *ServiceImpl) GetPermissionById(pId uint64) *model.Permission {
	var permission model.Permission
	if err := s.db.Where("id = ?", pId).First(&permission).Error; err != nil {
		if !gorm.IsRecordNotFoundError(err) {
			log.Error("get permissionById error: ", err.Error())
		}
		return nil
	} else {
		return &permission
	}
}

func (s *ServiceImpl) UpdatePermission(permission *model.Permission) error {
	return s.db.Save(permission).Error
}

func (s *ServiceImpl) ReplacePermissionAPIs(permission *model.Permission, apis []*model.API) error {
	return s.db.Model(permission).Association("APIs").Replace(apis).Error
}

func (s *ServiceImpl) AddAPI(api *model.API) error {
	return s.db.Create(api).Error
}

func (s *ServiceImpl) DeletePermission(permissionId uint64) error {
	return s.db.Unscoped().Where(
		"id = ?", permissionId,
	).Delete(&model.Permission{}).Error
}

func (s *ServiceImpl) GetAPIById(apiId uint64) *model.API {
	var api = new(model.API)
	err := s.db.Where("id = ?", apiId).First(api).Error
	if err != nil {
		if gorm.IsRecordNotFoundError(err) {
			return nil
		}
		log.Error("repo.GetAPIById error: " + err.Error())
	}
	return api
}

func (s *ServiceImpl) UpdateAPI(api *model.API) error {
	return s.db.Save(api).Error
}

func (s *ServiceImpl) DeleteAPI(apiId uint64) error {
	return s.db.Unscoped().Where(
		"id = ?", apiId,
	).Delete(&model.API{}).Error
}

func (s *ServiceImpl) ListAPIs(req *params.APIListRequest) (apis []*model.API, total uint64, err error) {
	db := DefaultListDB(s.db, &req.ListRequest)
	if req.Name != "" {
		db = db.Where("name = ?", req.Name)
	}
	if req.Code != "" {
		db = db.Where("code = ?", req.Code)
	}
	if len(req.PermissionIds) != 0{
		db = db.Joins("join oauth_permission_api_relation  relation on relation.api_id = oauth_api.id ").
		Where("relation.permission_id in (?)", req.PermissionIds)
	}
	db.Model(model.API{}).Count(&total)
	db = LimitDB(db, req.Limit, req.Offset)
	keyList := []string{
		"id","partner_id","name","code","url","method","description","remark",
	}
	if len(req.PermissionIds) > 0 {
		keyList = append(keyList,"permission_id")
	}
	err = db.Select(keyList).Find(&apis).Error
	if err != nil || len(apis) == 0 {
		return make([]*model.API, 0), 0, err
	}
	return apis, total, nil
}

func (s *ServiceImpl) GetHexPartner() *model.Partner {
	var partner model.Partner
	err := s.db.Where("code = ?", "hex").First(&partner).Error
	if err != nil {
		return nil
	}
	return &partner
}

func (s *ServiceImpl) ListAdminUser(req *params.UserListRequest) (users []*model.User, total uint64, err error) {
	hexPartner := s.GetHexPartner()
	if hexPartner == nil {
		return nil, 0, errors.New("no hex partner")
	}
	req.PartnerId = hexPartner.Id
	return s.ListUser(req)
}

func (s *ServiceImpl) ListClient(req *params.ClientListRequest) (clients []*model.Client, total uint64, err error) {
	db := DefaultListDB(s.db, &req.ListRequest)
	if !req.AllScope {
		db = db.Where("scope_id = ?", req.ScopeId)
	}
	if len(req.Name) != 0 {
		db = db.Where("name = ?", req.Name)
	}
	if len(req.ClientId) != 0 {
		db = db.Where("client_id = ?", req.Name)
	}
	db.Model(model.API{}).Count(&total)
	db = LimitDB(db, req.Limit, req.Offset)
	err = db.Find(&clients).Error
	if err != nil || len(clients) == 0 {
		return make([]*model.Client, 0), 0, err
	}
	return clients, total, nil
}

func (s *ServiceImpl) listUserAPIs(partnerId, userId uint64) ([]*model.API, error) {
	roleListReq := params.RoleListRequest{
		UserIds: []uint64{userId},
		ListRequest: params.ListRequest{
			PartnerId: partnerId,
		},
	}

	roles, _, err := s.ListRoles(&roleListReq)
	if err != nil {
		log.Error("list roles fail:", err)
		return nil, err
	}

	var rolesIds = make([]uint64, len(roles))
	for i, role := range roles {
		rolesIds[i] = role.Id
	}
	if len(roles) == 0 {
		return []*model.API{}, nil
	}

	permissionListReq := params.PermissionListRequest{
		ListRequest: params.ListRequest{
			PartnerId: partnerId,
		},
		RoleIds: rolesIds,
	}
	permissions, _, err := s.ListPermissions(&permissionListReq)
	if err != nil {
		log.Error("list permissions fail!")
		return nil, err
	}

	permissionListReq.RoleIds = nil
	allPermissionList,_,err := s.ListPermissions(&permissionListReq)
	if err != nil{
		log.Error("list all permissions fail!")
		return nil, err
	}
	parentMap := map[uint64]*model.Permission{}
	for _, item := range permissions {
		parentMap[item.Id] = item
	}
	// get current root tree child permission
	for len(parentMap) > 0{
		nextMap := map[uint64]*model.Permission{}
		for _, item := range allPermissionList {
			if _,ok := parentMap[item.ParentId];ok{
				nextMap[item.Id] = item
				permissions = append(permissions,item)
			}
		}
		parentMap = nextMap
	}

	var permissionIds = make([]uint64, len(permissions))
	for i, permission := range permissions {
		permissionIds[i] = permission.Id
	}
	if len(permissions) == 0 {
		return []*model.API{}, nil
	}
	apiListReq := params.APIListRequest{
		ListRequest: params.ListRequest{
			PartnerId: partnerId,
		},
		PermissionIds: permissionIds,
	}
	apis, _, err := s.ListAPIs(&apiListReq)
	if err != nil {
		log.Error("list apis fail!")
		return nil, err
	}
	return apis, nil
}

func (s *ServiceImpl) CheckAPIPermission(partnerId, userId uint64, apiCode string) (bool, error) {
	apiCode = strings.ToUpper(strings.TrimSpace(apiCode))
	hitCache, exist, err := s.cacheService.HasAPIPermission(partnerId, userId, apiCode)
	LogError(err, "check api permission from cache")
	if hitCache && exist {
		return exist, nil
	}
	apis, err := s.listUserAPIs(partnerId, userId)
	apiCodes := make([]string, len(apis))
	hasPermission := false

	for i, api := range apis {
		api.Code = strings.ToUpper(strings.TrimSpace(api.Code))
		apiCodes[i] = api.Code
		if apiCode == api.Code {
			hasPermission = true
		}
	}
	err = s.cacheService.SetUserAPICodes(partnerId, userId, apiCodes, time.Second*10*60)
	if err != nil {
		log.Error("set user api codes error : ", err.Error())
	}
	return hasPermission, nil
}

func (s *ServiceImpl) GetPermissionByLan(lanType, name string) *model.Permission {
	p := &model.Permission{}
	db := s.db
	err := db.Where(fmt.Sprintf(`content->>'%s' = ?`,lanType),name).First(p).Error;if err != nil{
		return nil
	}
	return p
}

func (s *ServiceImpl) GetRoleByLan(lanType, name string) *model.Role {
	r := &model.Role{}
	db := s.db
	err := db.Where(fmt.Sprintf(`content->>'%s' = ?`, lanType), name).First(r).Error;
	if err != nil {
		return nil
	}
	return r
}

func (s *ServiceImpl)BatchAddPermissionMethod(partnerId uint64,serviceName string,methodList []*model.Method)error  {
	tx := s.db.Begin()
	defer func() {
		if r := recover(); r != nil {
			tx.Rollback()
		}
	}()
	db := tx.Where("partnerId = ?",partnerId)
	db = tx.Where("service_name = ?",serviceName)
	//err := db.Delete(&model.Method{}).Error;if err != nil{
	//	tx.Rollback()
	//	return err
	//}
	validMethodList := make([]*model.Method,0)
	for _,method := range methodList {
		db = tx.Where("method_name = ?",method.MethodName)
		item := &model.Method{}
		err := db.Find(item).Error; if err != nil{
			if err != sql.ErrNoRows{
				tx.Rollback()
				return err
			}
			validMethodList = append(validMethodList,method)
		}
	}

	for _, method := range validMethodList {
		err := tx.Create(method).Error;if err != nil{
			tx.Rollback()
			return err
		}
	}
	return tx.Commit().Error
}
